/*
 * VRMLObject.java
 * 
 * Loads a VRML object from a file. As this class sub-classes
 * TransformGroup, 
 *
 * @author Andrew Pennington
 * @updated Andy C - Added lighting to textures. Now uses Java functions to retrieve
 *                   platform's file separator
 */
import java.io.File;
import javax.vecmath.*;
import javax.media.j3d.*;
import java.util.Enumeration;
import com.sun.j3d.loaders.Scene;

public class VRMLObject extends TransformGroup {


    /** the file extension for VRML scenes */
    private static final String SCENE_EXT = "wrl";
	
    /** the base directory for the models */
    private static final String BASE = "models" + File.separator;
    
    /** the source VRML scene */
    private Scene vrmlScene = null;


    /** load the VRML object with texture lighting disabled.
     * @param modelName the name of the model to load
     */
    public VRMLObject(String modelName){
        this(modelName, true);
    }

    public VRMLObject(String modelName, Vector3d location) {
        this(modelName, location, true);
    }

    public VRMLObject(String modelName, boolean isTextureLightingEnabled) {
        this(modelName, new Vector3d(), isTextureLightingEnabled);
    }
    
    public VRMLObject(String modelName, Vector3d location, boolean isTextureLightingEnabled) {

        super();

        com.sun.j3d.loaders.vrml97.VrmlLoader loader = new com.sun.j3d.loaders.vrml97.VrmlLoader();

        try {
            vrmlScene = loader.load(BASE + modelName + "." + SCENE_EXT);
        } catch (Exception ex){
            ex.printStackTrace();
            System.err.print("Exception loading file! From path:" + BASE + modelName + "." + SCENE_EXT);
            System.exit(1);
        }

        // get the scene group from the loaded VRML scene
        BranchGroup sceneGroup = vrmlScene.getSceneGroup();
        sceneGroup.setCapability(BranchGroup.ALLOW_DETACH);
        sceneGroup.setCapability(BranchGroup.ALLOW_BOUNDS_READ);

        if (isTextureLightingEnabled)
            enableTextureLighting(sceneGroup);

        /* locate the object in the world */
        Transform3D transform = new Transform3D();
        this.getTransform(transform);
        transform.setTranslation(location);
        this.setTransform(transform);

        this.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
        this.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);

        this.setUserData("Transform");
        this.addChild(sceneGroup);
        setAllUserData(sceneGroup, this);

    }
	
    /** 
     * recursively enable lighting on a particular branch group.
     * Ugly hack around the VRML97 loader, which does not correctly 
     * set texturing aspects of the scene. 
     *
     * @param bg the group of objects on which to enable lighting
     */

    private static void enableTextureLighting(Group bg){
        Enumeration children = bg.getAllChildren();
        while (children.hasMoreElements()){
            Object o = children.nextElement();
            if (o instanceof Group){
                enableTextureLighting((Group)o);
            } else if (o instanceof Shape3D){
                Appearance app = ((Shape3D)o).getAppearance();
                if (app == null)
                    app = new Appearance();
                Material mat = app.getMaterial();
                if (mat == null)
                    mat = new Material();
                mat.setLightingEnable(true);
                mat.setDiffuseColor(new Color3f(1,1,1));
                TextureAttributes textatt = app.getTextureAttributes();
                if (textatt == null)
                    textatt = new TextureAttributes();
                textatt.setTextureMode(TextureAttributes.MODULATE);
                app.setTextureAttributes(textatt);
            }
        }
    }

	/**
	 * recursive sets user data on objects in the passed group. Useful
	 * for picking (using the getUserData() method).
	 *
	 * @param bg	the group of objects on which to set data
	 * @param data  the user data to be set on all the objects of the group
	 */
    public static void setAllUserData(Group bg, Object data){

		Node child;
		Enumeration children = bg.getAllChildren ();
		
		while (children.hasMoreElements ()) {
		    child = (Node) children.nextElement();
		    if (child instanceof Group){
		        setAllUserData ((Group)child, data);
		    } else {
		        child.setUserData (data);
		    }
		}
    }

	/**
	 * returns a transform group containing the loaded scene.
	 * @returns the scene transform group 
	 */
    public TransformGroup getTransformGroup(){
		return this;
    }
}
